_ = require 'lodash'
sizeof = require 'object-sizeof'

log = require '../log'
util = require '../util'

Meta = require '../meta/Meta'

# 缓存分两类：1、byIdCache：根据 ID 查询单个实体。2、otherCache：其他，包括根据非 ID 查询单个实体。
# 增删改三个操作。增不影响 byIdCache；删和改影响指定 ID 的 byIdCache；
# 但增可能影响所有 otherCache。比如我们查询最新插入一个的实体，新增会导致缓存失效。更新、删除类似。
# TODO 其实还有一个"根据多个ID查询"。增不影响。修改、删除时检查被操作的ID是否在这些ID中，不在就不需要删除缓存。

byIdCache = {}
otherCache = {}

entityCreatedListeners = []
entityUpdatedListeners = []
entityRemovedListeners = []

stats = {query: 0, miss: 0}
accessCounter = {}

cleanTimer = null

incAccessCounter = (entityName, cacheId)->
    id = "#{entityName}/#{cacheId}"
    c = accessCounter[id]
    if c
        accessCounter[id] = c + 1
    else
        accessCounter[id] = 1

gWithCache = (entityName, cache, cacheId, gQuery)->
    stats.query++
    incAccessCounter(entityName, cacheId)

    entityMeta = Meta.getEntityMeta(entityName)
    noServiceCache = entityMeta.noServiceCache

    if not noServiceCache
        entityCache = util.setIfNone cache, entityName, {}
        cacheItem = entityCache[cacheId]
        if cacheItem?
            return _.cloneDeep(cacheItem.value) # 返回拷贝防止污染缓存

    stats.miss++
    freshValue = yield from gQuery()

    if noServiceCache
        freshValue
    else
        # 这里 Null 值也进入缓存，防止 404 反复触发数据库查询
        entityCache[cacheId] = {value: freshValue, createdOn: Date.now()}
        _.cloneDeep(freshValue) # 返回拷贝防止污染缓存

removeByIdCache = (entityName, ids)->
    entityByIdCache = byIdCache[entityName]
    if entityByIdCache? and ids?
        for id in ids
            for cacheId of entityByIdCache
                if cacheId.indexOf(id + "|") == 0 # 缓存ID以实体ID开头
                    delete entityByIdCache[cacheId]

exports.gWithByIdCache = (entityName, cacheId, gQuery)->
    yield from gWithCache(entityName, byIdCache, cacheId, gQuery)

exports.gWithOtherCache = (entityName, cacheId, gQuery)->
    yield from gWithCache(entityName, otherCache, cacheId, gQuery)

exports.gFireEntityCreated = (ctx, entityName) ->
    otherCache[entityName] = {}

    for l in entityCreatedListeners
        try
            if util.isGenerator(l)
                yield from l(ctx, entityName)
            else
                l(ctx, entityName)
        catch e
            log.system.error e, "fireEntityCreated"
            throw e
    yield return

exports.gFireEntityUpdated = (ctx, entityName, ids) ->
    otherCache[entityName] = {}

    removeByIdCache entityName, ids

    for l in entityUpdatedListeners
        try
            if util.isGenerator(l)
                yield from l(ctx, entityName, ids)
            else
                l(ctx, entityName, ids)
        catch e
            log.system.error e, "onEntityUpdated"
            throw e

exports.gFireEntityRemoved = (ctx, entityName, ids) ->
    otherCache[entityName] = {}

    removeByIdCache entityName, ids

    for l in entityRemovedListeners
        try
            if util.isGenerator(l)
                yield from l(ctx, entityName, ids)
            else
                l(ctx, entityName, ids)
        catch e
            log.system.error e, "onEntityRemoved"
            throw e

exports.onEntityCreated = (listener)->
    entityCreatedListeners.push listener

exports.onEntityUpdated = (listener)->
    entityUpdatedListeners.push listener

exports.onEntityRemoved = (listener)->
    entityRemovedListeners.push listener

exports.onUpdatedOrRemoved = (listener)->
    entityUpdatedListeners.push listener
    entityRemovedListeners.push listener

exports.gClearCache = ->
    byIdCache = {}
    otherCache = {}
    yield return

exports.gClearStats = ->
    stats.query = 0
    stats.miss = 0
    yield return

exports.gGetStats = ->
    stats.cacheSizeInBytes = (sizeof(byIdCache) + sizeof(otherCache)) / 1024 / 1024
    this.body = stats
    yield return

exports.gGetTopAccess = ->
    counters = []
    for name, count of accessCounter
        counters.push {name, count}

    this.body = _.sortBy counters, (c)-> -c.count
    yield return

exports.startCleanTimer = ->
    interval = 10 * 60 * 1000 # 10 分钟检查一次
    cleanTimer = setInterval((->
        earliest = Date.now() - 6 * 60 * 60 * 1000 # 缓存 6 小时
        for id, item of byIdCache
            delete byIdCache[id] if item.createdOn < earliest
        for id, item of otherCache
            delete otherCache[id] if item.createdOn < earliest
    ), interval)

exports.stopCleanTimer = ->
    clearInterval(cleanTimer) if cleanTimer?



